package testing.rubeloader;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import ship.Ship;

import box.Scene;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.Input.Buttons;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Texture.TextureWrap;
import com.badlogic.gdx.graphics.g2d.PolygonRegion;
import com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.Box2DDebugRenderer;
import com.badlogic.gdx.physics.box2d.Contact;
import com.badlogic.gdx.physics.box2d.ContactImpulse;
import com.badlogic.gdx.physics.box2d.ContactListener;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.Manifold;
import com.badlogic.gdx.physics.box2d.PolygonShape;
import com.badlogic.gdx.physics.box2d.Shape;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import com.badlogic.gdx.utils.Array;
import com.gushikustudios.rube.PolySpatial;
import com.gushikustudios.rube.RubeScene;
import com.gushikustudios.rube.SimpleSpatial;
import com.gushikustudios.rube.loader.RubeSceneLoader;
import com.gushikustudios.rube.loader.serializers.utils.RubeImage;

public class Renderer implements ApplicationListener, InputProcessor, ContactListener {
	
	private String fName;
	
	private OrthographicCamera camera;
	private Scene scene;
	private Box2DDebugRenderer debugRender;
	
	private Array<SimpleSpatial> spatials; // used for rendering rube images
	private Array<PolySpatial> polySpatials;
	private Map<String, Texture> textureMap;
	private Map<Texture, TextureRegion> textureRegionMap;
	
	private static final Vector2 mTmp = new Vector2(); // shared by all objects
	private SpriteBatch batch;
	private PolygonSpriteBatch polygonBatch;

	// used for pan and scanning with the mouse.
	private Vector3 mCamPos;
	private Vector3 mCurrentPos;

	// used for converting screen's coordinates to world's
	private float k = 1.0f / 8.0f; //ratio between screen's and world's coordinates
	
	private World mWorld;

	private float mAccumulator; // time accumulator to fix the physics step.

	private int mVelocityIter = 8;
	private int mPositionIter = 3;
	private float mSecondsPerStep = 1 / 60f;

	private static final float MAX_DELTA_TIME = 0.25f;

	public Renderer(String fName) {
		super();
		this.fName = fName;
	}
	
	@Override
	public void create() {
		float w = Gdx.graphics.getWidth();
		float h = Gdx.graphics.getHeight();

		Gdx.input.setInputProcessor(this);

		mCamPos = new Vector3();
		mCurrentPos = new Vector3();

		camera = new OrthographicCamera(100, 100 * h / w);
		camera.position.set(50, -50 * h / w, 0);
		camera.zoom = 1.0f;
		camera.update();
		
		scene = new Scene(new RubeSceneLoader().loadScene(Gdx.files.internal(fName)));
		
		debugRender = new Box2DDebugRenderer();

		batch = new SpriteBatch();
		polygonBatch = new PolygonSpriteBatch();

		textureMap = new HashMap<String, Texture>();
		textureRegionMap = new HashMap<Texture, TextureRegion>();

		createSpatialsFromRubeImages(scene.getScene());
		createPolySpatialsFromRubeFixtures(scene.getScene());

		mWorld = scene.getScene().getWorld();
		// configure simulation settings
		mVelocityIter = scene.getScene().velocityIterations;
		mPositionIter = scene.getScene().positionIterations;
		if (scene.getScene().stepsPerSecond != 0) {
			mSecondsPerStep = 1f / scene.getScene().stepsPerSecond;
		}
		mWorld.setContactListener(this);
		
		scene.getScene().printStats();
		scene.getScene().clear(); // no longer need any scene.getScene() references
	}

	@Override
	public void dispose() {}

  	@Override
  	public boolean keyDown(int keycode) {
  		//Block of choosing weapon
  		{
  			if (keycode == Keys.NUM_1) {scene.getShip().chooseWeapon(1);}
  			if (keycode == Keys.NUM_2) {scene.getShip().chooseWeapon(2);}
  			if (keycode == Keys.NUM_3) {scene.getShip().chooseWeapon(3);}
  			if (keycode == Keys.NUM_4) {scene.getShip().chooseWeapon(4);}
  			if (keycode == Keys.NUM_5) {scene.getShip().chooseWeapon(5);}
  			if (keycode == Keys.NUM_6) {scene.getShip().chooseWeapon(6);}
  		}
  		
  		return false;
  	}

   @Override
   public boolean keyUp(int keycode) {
	   return false;
   }

   @Override
   	public boolean keyTyped(char character) {
	   
	   return false;
   	}

   public Vector2 getCameraPointPosition(Vector2 point) {
	   Vector3 vec3 = new Vector3(point.x, point.y, 0);
	   camera.unproject(vec3);
	   
	   return new Vector2(vec3.x, vec3.y); 
   }
   
   @Override
   	public boolean touchDown(int screenX, int screenY, int pointer, int button) {
	   //System.out.println("Mouse down");
	   switch (button) {
	   	case Buttons.LEFT:
	   		Vector2 point = this.getCameraPointPosition(new Vector2(screenX, screenY));
	   		scene.getShip().mouseDown(point);
	   		break;
	   	case Buttons.RIGHT:
	   		mCamPos.set(screenX, screenY, 0);
	   		camera.unproject(mCamPos);
	   		break;
	   	default: break;
	   }
	   return true;
   	}

   	@Override
   	public boolean touchUp(int screenX, int screenY, int pointer, int button) {
   		//System.out.println("Mouse up");
   		switch (button) {
	   	case Buttons.LEFT:
	   		Vector2 point = this.getCameraPointPosition(new Vector2(screenX, screenY));
	   		scene.getShip().mouseUp(point);
	   		break;
	   	default: break;
	   }
 	   return false;
   	}

   	@Override
   	public boolean touchDragged(int screenX, int screenY, int pointer) {
   		//System.out.println("Mouse move");
   		
   		boolean leftPressed = Gdx.input.isButtonPressed(Input.Buttons.LEFT);
   		boolean rightPressed = Gdx.input.isButtonPressed(Input.Buttons.RIGHT);
   		
   		if (leftPressed) {
   			Vector2 point = this.getCameraPointPosition(new Vector2(screenX, screenY));
	   		scene.getShip().mouseMoving(point);
   		}
   		
   		if (rightPressed) {
   			mCurrentPos.set(screenX, screenY, 0);
   			camera.unproject(mCurrentPos);
   			camera.position.sub(mCurrentPos.sub(mCamPos));
   			camera.update();
   		}
   		return true;
   	}

   	@Override
   	public boolean mouseMoved(int screenX, int screenY) {
   		return false;
   	}

   	@Override
   	public boolean scrolled(int amount) {
   		camera.zoom += (amount * 0.1f);
   		if (camera.zoom < 0.1f) {
   			camera.zoom = 0.1f;
   		}
   		//k = camera.zoom / 7.0f;
   		System.out.println(camera.zoom);
   		System.out.println(k);
   		camera.update();
   		return true;
   	}
	
	@Override
	public void render() {
		//Keyboard block
		{
			boolean isWPressed = Gdx.input.isKeyPressed(Keys.W);
			boolean isSPressed = Gdx.input.isKeyPressed(Keys.S);
			boolean isAPressed = Gdx.input.isKeyPressed(Keys.A);
			boolean isDPressed = Gdx.input.isKeyPressed(Keys.D);
			
			Vector2 velocity = new Vector2(0.0f, 0.0f);
  			final float V = 100.0f;
  			if (isWPressed) {velocity.add(new Vector2(0.0f, V));}
  			if (isSPressed) {velocity.add(new Vector2(0.0f, -V));}
  			if (isAPressed) {velocity.add(new Vector2(-V, 0.0f));}
  			if (isDPressed) {velocity.add(new Vector2(V, 0.0f));}
  			
  			scene.getShip().move(velocity);
  			
		}
		
		//Drawing block
		Gdx.gl.glClearColor(0, 0, 0, 1);
		Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

		float delta = Gdx.graphics.getDeltaTime();

		if (delta > MAX_DELTA_TIME) {
			delta = MAX_DELTA_TIME;
		}

		mAccumulator += delta;

		while (mAccumulator >= mSecondsPerStep) {
			mWorld.step(mSecondsPerStep, mVelocityIter, mPositionIter);
			mAccumulator -= mSecondsPerStep;
		}

		if ((spatials != null) && (spatials.size > 0)) {
			batch.setProjectionMatrix(camera.combined);
			batch.begin();
			for (int i = 0; i < spatials.size; i++) {
				spatials.get(i).render(batch, 0);
			}
			batch.end();
		}

		if ((polySpatials != null) && (polySpatials.size > 0)) {
        	polygonBatch.setProjectionMatrix(camera.combined);
        	polygonBatch.begin();
        	for (int i = 0; i < polySpatials.size; i++) {
        		polySpatials.get(i).render(polygonBatch, 0);
        	}
        	polygonBatch.end();
		}

		debugRender.render(mWorld, camera.combined);
	}

	/**
	 * Creates an array of SimpleSpatial objects from RubeImages.
	 * 
	 * @param scene2
	 */
	private void createSpatialsFromRubeImages(RubeScene scene) {
		Array<RubeImage> images = scene.getImages();
		if ((images != null) && (images.size > 0)) {
			spatials = new Array<SimpleSpatial>();
			for (int i = 0; i < images.size; i++) {
				RubeImage image = images.get(i);
				mTmp.set(image.width, image.height);
            	String textureFileName = "data/" + image.file;
            	Texture texture = textureMap.get(textureFileName);
            	if (texture == null) {
            		texture = new Texture(textureFileName);
            		textureMap.put(textureFileName, texture);
            	}
            	SimpleSpatial spatial = new SimpleSpatial(texture, image.flip, image.body, image.color, mTmp, image.center,image.angleInRads * MathUtils.radiansToDegrees);
            	spatials.add(spatial);
			}
		}
	}

	/**
	 * Creates an array of PolySpatials based on fixture information from the scene. Note that
	 * fixtures create aligned textures.
	 * 
	 * @param scene
	 */
	private void createPolySpatialsFromRubeFixtures(RubeScene scene) {
		Array<Body> bodies = scene.getBodies();

		if ((bodies != null) && (bodies.size > 0)) {
			polySpatials = new Array<PolySpatial>();
			Vector2 bodyPos = new Vector2();
			// for each body in the scene...
			for (int i = 0; i < bodies.size; i++) {
				Body body = bodies.get(i);
				bodyPos.set(body.getPosition());

				ArrayList<Fixture> fixtures = body.getFixtureList();

				if ((fixtures != null) && (fixtures.size() > 0)) {
					// for each fixture on the body...
					for (int j = 0; j < fixtures.size(); j++) {
						Fixture fixture = fixtures.get(j);

						String textureName = scene.getCustom(fixture, "TextureMask", (String) null);
						if (textureName != null) {
							String textureFileName = "data/" + textureName;
							Texture texture = textureMap.get(textureFileName);
							TextureRegion textureRegion = null;
							if (texture == null) {
								texture = new Texture(textureFileName);
								texture.setWrap(TextureWrap.Repeat, TextureWrap.Repeat);
								textureMap.put(textureFileName, texture);
								textureRegion = new TextureRegion(texture);
								textureRegionMap.put(texture, textureRegion);
							}
							else {
								textureRegion = textureRegionMap.get(texture);
							}

							// only handle polygons at this point -- no chain, edge, or circle fixtures.
							if (fixture.getType() == Shape.Type.Polygon) {
								PolygonShape shape = (PolygonShape) fixture.getShape();
								int vertexCount = shape.getVertexCount();
								float[] vertices = new float[vertexCount * 2];

								// static bodies are texture aligned and do not get drawn based off of the related body.
								if (body.getType() == BodyType.StaticBody) {
									for (int k = 0; k < vertexCount; k++) {

										shape.getVertex(k, mTmp);
										mTmp.rotate(body.getAngle() * MathUtils.radiansToDegrees);
										mTmp.add(bodyPos); // convert local coordinates to world coordinates to that textures are
                                                 // aligned
										vertices[k * 2] = mTmp.x * PolySpatial.PIXELS_PER_METER;
										vertices[k * 2 + 1] = mTmp.y * PolySpatial.PIXELS_PER_METER;
									}
									PolygonRegion region = new PolygonRegion(textureRegion, vertices);
									PolySpatial spatial = new PolySpatial(region, Color.WHITE);
									polySpatials.add(spatial);
								}
								else {
									// all other fixtures are aligned based on their associated body.
									for (int k = 0; k < vertexCount; k++) {
										shape.getVertex(k, mTmp);
										vertices[k * 2] = mTmp.x * PolySpatial.PIXELS_PER_METER;
										vertices[k * 2 + 1] = mTmp.y * PolySpatial.PIXELS_PER_METER;
									}
									PolygonRegion region = new PolygonRegion(textureRegion, vertices);
									PolySpatial spatial = new PolySpatial(region, body, Color.WHITE);
									polySpatials.add(spatial);
								}
							}
						}
					}
				}
			}
		}
	}

	@Override
	public void resize(int width, int height) {}


  	@Override
  	public void pause() {}

  	@Override
  	public void resume()  {}

   	@Override
   	public void beginContact(Contact contact) {}


   @Override
   public void endContact(Contact contact) {}

   @Override
   public void preSolve(Contact contact, Manifold oldManifold) {}

   @Override
   public void postSolve(Contact contact, ContactImpulse impulse) {}
}

